import { user as useUser } from "@/store"
import { uploadFileReq } from "@/common/request"
import  {storePrefix} from "@/common/config"
import {hasAuth} from "@/common/addGlobalProperty"
import type{Ref} from "vue"
import {nextTick} from "vue"
 

/**
 * @description 登录验证
 */
const isLogin = () => {
   if (getToken()) return true
   return false
}

/**
 * @description 获取token
 */
const getToken = ():string => {
   const user = useUser()
   return user.info.token as string
}

export type bsType={
   getItem:(key: string)=>any
   setItem:(key: string, value: any, expire?:number)=>void
   removeItem:(key: string)=>void,
   clear:()=>void
   [key:string | symbol]:any
}

/**
 * @description 创建自定义存储对象的函数
 * @param config 
 * @param config.prefix    存储key的前缀，有默认值
 * @param config.isEncrypt  是否加密存储数据
 * @param config.storeWay   存储方式 localStorage | sessionStorage
 */
const creatBs = (config?:{prefix?:string,isEncrypt?: boolean,storeWay?:'localStorage' | 'sessionStorage',}
):bsType => {
    const {
      prefix=storePrefix,
      isEncrypt=import.meta.env.VITE_NODE_ENV != 'dev',
      storeWay='localStorage',
   }=config?config:{}
   
   const configKey = Symbol("config") 
   
   return {
      [configKey]: {prefix,isEncrypt,storeWay:storeWay==='localStorage'?localStorage:sessionStorage},
      getItem(key: string) {
         let obj = this[configKey].storeWay.getItem(`${this[configKey].prefix}_${key}`) as any
         if (!obj) return null
         if (this[configKey].isEncrypt)
            obj = decodeURIComponent(obj)
         try {
            obj = JSON.parse(obj)
         } catch (error) {
            if(import.meta.env.VITE_NODE_ENV == 'dev') console.log(`数据损坏 ${key}`)
            this.removeItem(key)
            return null
         }
         if (obj.expire && Date.now() >= obj.expire) {
            if(import.meta.env.VITE_NODE_ENV == 'dev')
            {
               console.log(`存储到期的时间:${new Date(obj.expire).toLocaleString()}   ${key}`)
               console.log(`目前的时间:${new Date().toLocaleString()}   ${key}`)
            }
            this.removeItem(key)
            return null
         }
         
         if(key==="atdv_vue_pinia_storagefy_user" && import.meta.env.VITE_NODE_ENV == 'dev') 
         console.log('token到期时间',obj.expire,new Date(obj.expire).toLocaleString())

         return obj.value ? obj.value : null
      },
      setItem(key: string, value: any, expire = 0) {
         let storeValue = JSON.stringify({
            value,
            expire: expire ? Date.now() + expire : 0,
         })
         if (this[configKey].isEncrypt) storeValue = encodeURIComponent(storeValue)
         // 默认不过期，单位ms
         this[configKey].storeWay.setItem(`${this[configKey].prefix}_${key}`, storeValue)
      },
      removeItem(key: string) {
         this[configKey].storeWay.removeItem(`${this[configKey].prefix}_${key}`)
      },
      clear() {
         this[configKey].storeWay.clear()
      }
   }
}
const bs=creatBs()


/**
   * @description 读取某个对象的属性值，同 lodash.get
   * @param obj 读取的对象
   * @param key 读取的键名，例：'k'或'k1.k2'或'k1.k2.k3'   
   * @returns 对应属性的值
   */
const get=(obj:objType,key:string)=>{
   let res:any
   key.split('.').forEach((k,i)=>{if(i==0) res=obj[k]; else res=res[k]})
   return res
 }

/**
 * @description 文件下载，传axios的完整响应，需要blob
 * @param res   接口响应的完整结果
 * @param blobKey 从res中取出blob的key，例：'data'或者'data.data'，默认值为'data'
 * @param name  文件名
 */
const downloadFileByBlob = (res: objType,blobKey:string='data',name?:string) => {
   const content = res.headers['content-disposition'];
   const filename = content ? content.split('"')[1] : '未知文件'
   const a = document.createElement('a');
   a.href = window.URL.createObjectURL(get(res,blobKey))
   a.download =name? name:filename;
   a.click();
   window.URL.revokeObjectURL(a.href)
}

/**
 * @description 通过文件流触发文件打印
 * @param blob  文件的blob
 */
const printFileByBlob=(blob:any)=>{
   const id="print-iframe"
   const oldPrintIframe = document.getElementById(id);
   if(oldPrintIframe) oldPrintIframe.remove()
   const printIframe = document.createElement('iframe')
   printIframe.id=id
   printIframe.style.display = 'none'
   printIframe.src = URL.createObjectURL(blob)
   document.body.appendChild(printIframe)
   setTimeout(()=>{
     printIframe.contentWindow?.print()
     URL.revokeObjectURL(printIframe.src)
   },100)
}

/**
 * @description 文件流转下载路径。
 * @param res 接口响应的完整结果
 * @param streamKey  从res中取出b文件流的key，例：'data'或者'data.data'，默认值为'data'
 * @returns object{url:生成的url,releaseUrl:释放url的函数}
 */
const fileStreamToUrl = (res: any,streamKey:string='data') => {
   const fileType = res.headers['content-type']
   const blob = new Blob([get(res,streamKey)], {
      type: fileType,
   })
   const url=window.URL.createObjectURL(blob)
   return {
      url,
      releaseUrl:window.URL.revokeObjectURL(url)
   }
}


/**
 * @description 文件上传，传递文件上传时的事件源对象
 * @param file  单文件上传时为文件对象，多文件上传时为文件数组
 * @param fieldName  后端接收文件的字段名
 * @param type 当文件单个还是多个文件上传
 * @param config 额外需要追加的信息
 */
const uploadFile = async (file:any,fieldName:string='file',type:'single' | 'multiple'='single',config?:objType) => {
   const formData = new FormData()
   let reqUrl:string
   if(type=="single"){
      reqUrl="/uploadFile"
      formData.append(fieldName, file)
   }else{
      if(!Array.isArray(file)) throw new Error("请传递文件数组！")
      reqUrl="/uploadFiles"
      for (let fileObj of file) {
         formData.append(fieldName, fileObj)
      }
   }
   if(config) Object.entries(config).forEach(([k,v])=>{formData.append(k,v)})
   const res = await uploadFileReq(reqUrl, formData)
   console.log(res)
   return res
}

/**
 * @description 节点数组转树对象
 * @param list 节点数组
 * @param idKeyName  节点id的键名
 * @param PidKeyName  父节点id的键名
 * @param isWellDeepClone  深克隆时函数正则等特殊对象是否保留，默认不保留，不保存时性能更佳
 * @param callback      访问每个节点时的回调
 * @param callback_findC 找到子节点时的回调
 * @returns 树形对象
 */
const listToTree = <nodeT=objType>(list:nodeT[],idKeyName:string,PidKeyName:string,isWellDeepClone=false,callback?:(node:nodeT)=>void,callback_findC?:(node:nodeT)=>void)=>{
   let _list
   if(isWellDeepClone) _list=deepClone(list)
   else _list=JSON.parse(JSON.stringify(list))
   let res
   for(let i of _list){
   for(let j of _list){
   if(!i[PidKeyName])res=i    
   if(i[idKeyName]==j[PidKeyName])
   {
     if(!i.children)i.children=[]
     i.children.push(j)
     callback_findC && callback_findC(i)
   }  
   }
   callback && callback(i)
   }
   return res
}



/**
 * @description 遍历树数组。可寻找目标节点，可单纯遍历 
 * @param treeArr     树数组
 * @param callback   寻找节点时，回调找到节点时执行；单纯遍历时访问每个节点时执行
 * @param idKeyName  id键盘名
 * @param targetId  目标节点id
 */
const traverseTreeArr=(treeArr:treeType[],callback?:(node:objType)=>void,idKeyName?:string,targetId?:string|number)=>{
   for (const v of treeArr) {
      if(targetId && idKeyName && v[idKeyName]==targetId) {    // 传了targetId和就是寻找节点
         callback&&callback(v)   
         return v
      }
      !targetId&&callback&&callback(v)     // 没传就正常遍历
      if(v.children) traverseTreeArr(v.children,callback,idKeyName,targetId)
   }
}


/**
 * @description 树数组转列表
 * @param treeArr  树数组
 * @param isWellDeepClone  深克隆时函数正则等特殊对象是否保留，默认不保留，不保存时性能更佳
 * @returns 树节点数组
 */
const treeArrToList=(treeArr:treeType[],isWellDeepClone=false)=>{
   const list:treeType[]=[]
   let treeList
   if(isWellDeepClone) treeList=deepClone(treeArr)
   else treeList=JSON.parse(JSON.stringify(treeArr))
   traverseTreeArr(treeList,(node)=>{list.push(node)})
   return list
}

/**
 * @description 数组的多规则排序。
 * @param arr 需要排序的数组
 * @param sortKeys 排序的字段集。按优先级顺序写，例：优先级rank->money->time,则 ['rank','money','time']
 * @param way 排序方式，默认升序
 * @param isWellDeepClone  深克隆时函数正则等特殊对象是否保留，默认不保留，不保存时性能更佳
 * @returns  排好序新的新数组，
 */
const sortManyRuleArr=(arr:objType[],sortKeys:string[],way:'up' | 'down'='up',isWellDeepClone=false)=>{
   const _arr=deepClone<objType[]>(arr,isWellDeepClone)
   const res:objType[][]=[]
   sortKeys.forEach((sortKey,i)=>{
      res[i]=[]
      _arr.forEach(v=>{
         if(Object.keys(v).includes(sortKey) && !v.del) {
            res[i].push(v)
            v.del=1
         }
      })
   })
   res.forEach((v,i)=>{
      const k=sortKeys[i]
      v.sort((a,b)=>{
         let pre=get(a,k),next=get(b,k)
         if(typeof pre!='number') pre=999999
         if(typeof next!='number') next=999999
         if(way=='up') return pre-next
         else return next-pre
      })
   })
   if(way=='up') return res.flat()
   else return res.reverse().flat()
}

/**
 * @description 优先按sortKey对应字段排序，其次按创建时间排序。
 * @param arr   需要排序的数组
 * @param sortKey  排序的字段名，按照该字段名排序
 * @param way      排序方式，默认升序
 * @param changeOrigin   是否改变原数组
 * @param isWellDeepClone  当不改变原数组时，得到的新数组中函数正则等特殊对象是否保留，默认不保留，不保存时性能更佳
 * @returns  排好序的数组
 */
const sortArr=(arr:objType[],sortKey:string, way:'up' | 'down'='up' ,changeOrigin = true, isWellDeepClone = false)=>{
   const _arr:any[] = changeOrigin ? arr : deepClone(arr, isWellDeepClone);
   _arr.sort((pre, next) => {
   let preR = get(pre,sortKey),nextR = get(next,sortKey)
   let preT=pre.createTime?pre.createTime:0
   let nextT=next.createTime?next.createTime:0
   if (!(typeof preR == "number" && !isNaN(preR))) preR = 999+preT;
   if (!(typeof nextR == "number" && !isNaN(nextR))) nextR = 999+nextT;
   if(way=='up') return preR - nextR;
   else return nextR-preR
   });
   return _arr;
}

/**
 * @description 优先按sortKey对应值排序，其次按创建时间排序。
 * @param treeArr   需要排序的树数组
 * @param sortKey  排序的字段名，按照该字段名排序
 * @param way      排序方式，默认升序
 * @param changeOrigin   是否改变原数组，默认改变
 * @param isWellDeepClone  当不改变原数组时，得到的新数组中函数正则等特殊对象是否保留，默认不保留，不保存时性能更佳
 * @returns  排好序的树数组
 */
const sortTreeArr=(treeArr:treeType[],sortKey:string,way:'up' | 'down'='up',changeOrigin = true, isWellDeepClone = false)=>{
   const res = sortArr(treeArr,sortKey,way,changeOrigin, isWellDeepClone);
   res.forEach((v) => {
      if (Array.isArray(v.children)) v.children = sortTreeArr(v.children,sortKey);
   });
   return res;
}
   


/**
 * @description  对象的深克隆，可以是数组或者对象
 * @param obj  需要克隆的对象
 * @param isWellDeepClone 深克隆时函数正则等特殊对象是否保留，默认保留，不保存时性能更佳
 * @returns 新对象
 */
const deepClone=<T=any>(obj:any,isWellDeepClone=true):T=>{
   if(!isWellDeepClone) return JSON.parse(JSON.stringify(obj))
   if(!Array.isArray(obj) && Object.prototype.toString.call(obj)!=='[object Object]'){
      return obj
   }
   const cloneobj:objAndArrType=Array.isArray(obj)?[]:{}
   for(const[k,v] of Object.entries(obj))
   {
   if(Array.isArray(v) || Object.prototype.toString.call(v)==='[object Object]')
   cloneobj[k]=deepClone(v)
   else  cloneobj[k]=v
   }
   return cloneobj as T
}

/**
 * @description 在body没有滚动条的情况下，获取布局内容区高度
 */
const getLayoutContentHeight=()=>{
   const content=document.querySelector('.BasicLayout .layout-rigth-main .content')
   // 20是content的内边距高度
   if(content) return parseInt(getComputedStyle(content as Element,null).minHeight) -24
   else throw new Error('请在组件的挂载钩子调用getLayoutContentHeight方法！')
}


export type setTableHeightParamsType={
   tableHeightRef:Ref
   otherSectionHeight?:number
   tToolHeight?:number
   paginationHeight?:number
   isTable?:boolean,
   isHandleTableDomHeight?:boolean
}

/**
 * @description 设置表格高度为在内容布局中可以占据的最大高度，如果浏览器高度小于680px则设置为undefined
 * @param config
 * @param config.tableHeightRef 控制表格高度的响应式数据
 * @param config.otherSectionHeight  内容布局中除去表格和表格工具栏外其他元素占据的高度，默认0。对于表格以外元素使用就是除去他自己以外部分的高度
 * @param config.tToolHeight  表格上面工具栏高度，默认52
 * @param config.paginationHeight  分页器高度，默认44
 * @param config.isTable       是否是为表格设置，默认是
 * @param config.isHandleTableDomHeight      当表格高度不能顶到底部时，是否要帮忙处理表格元素的高度，默认处理
 * @return 表格高度
 */
const setTableHeight=(config:setTableHeightParamsType)=>{
  const{tableHeightRef,otherSectionHeight=0,tToolHeight=52,paginationHeight=44,isTable=true,isHandleTableDomHeight=true}=config
  const tableDomMaxH=getLayoutContentHeight()-(otherSectionHeight+tToolHeight+paginationHeight)   
  let h:number | undefined
  if(window.innerHeight<680) {  
     tableHeightRef.value=''    // 当maxHeight值为undefined时，不执行高度重新赋值,''可以
     h=undefined
  }else{
     if(isTable) h=tableDomMaxH
     else h=getLayoutContentHeight()-otherSectionHeight
     tableHeightRef.value=h

     if(isTable && isHandleTableDomHeight){
      nextTick(()=>{
           //console.log(document.querySelector(".el-table"))
           const tableDom:HTMLElement | null=document.querySelector(".el-table")  
           if(tableDom){
            tableDom.style.height='auto'   
            const tableDomH=Math.ceil(tableDom!.offsetHeight)+1 // el-table有时不撑满maxHeight，留了个零点几，所以加一
            //console.log('hhhhhhhhhhh1',tableDomMaxH,tableDomH)
            if(tableDomH<tableDomMaxH)   
            {  
               // console.log('hhhhhhhhhhh2',tableDomMaxH,tableDomH)
               tableDom.style.height=tableDomMaxH + 'px'
            }
           }
      })
    }
  }  
  //console.log(h)
  return h
}


/**
 * @description 得到一个防抖函数
 * @param callback 需要防抖的函数
 * @param timeout  防抖时间时间间隔 
 * @param immediate 在防抖时间之前还是之后执行，默认在之前执行
 * @returns   防抖函数
 */
const getDounceFunc=(callback:()=>void,timeout=1500,immediate=true)=>{
   let debounceTimer:any=0
   return ()=>{
      if(!debounceTimer){
         if(immediate) {callback();debounceTimer=setTimeout(()=>{debounceTimer=0},timeout)}
         else {debounceTimer=setTimeout(()=>{callback();debounceTimer=0},timeout)}
      } 
      else{
         clearTimeout(debounceTimer)
         debounceTimer=setTimeout(()=>{
            if(!immediate) callback()
            debounceTimer=0
         },timeout)
      }
 }
}



/**
 * @description 得到一个节流函数
 * @param callback 需要节流的函数
 * @param timeout  防抖时间时间间隔 
 * @param immediate 在节流时间之前还是之后执行，默认在之前执行
 * @returns  节流函数
 */
const getThrottleFunc=(callback:()=>void,timeout=1500,immediate=true)=>{
let throttleTimer:any=0
  return ()=>{
   if(!throttleTimer){
      if(immediate){
         callback()
         throttleTimer=setTimeout(()=>{throttleTimer=0},timeout)
     }
     else {
      throttleTimer=setTimeout(()=>{callback();throttleTimer=0},timeout)
     }
   }
  }
}

/** 
 * @description 获取权限按钮占据的列宽。
 * @param fixedColumnActionKeys 需要授权按钮的权限值数组
 * @param columns 表头 
 * @param noNeedAuthBtnNum  无需授权按钮的数量
 * @param btnW 按钮宽度，默认48px
**/
const setFixedColumnWidth=(fixedColumnActionKeys:(string| string[])[],columns:Ref<tableColConfig[]>,noNeedAuthBtnNum=0,btnW=48)=>{
   let btnNum=0,w=0
   fixedColumnActionKeys.forEach(v=>{
      if(Array.isArray(v)){
         if(hasAuth(v)) btnNum++
      }
      else {
         if(hasAuth([v]))  btnNum++
      }
   })
   btnNum+=noNeedAuthBtnNum
   if(btnNum) w=btnNum*btnW + (btnNum-1)*12 + 10*2  // 12是按钮之间的间距，10是两边的留白
   else w=0     
   columns.value.forEach(v=>{
		if(v.label=='操作') {
			if(w)  v.width=w
			else {
            v.width=1      // 对于el-table的固定列宽，不能给0或者0px，给了不生效
            v.label=""
         }
		}
   })
   if(w) return w
   else return 1
}

export {
   creatBs,
   bs,
   get,
   isLogin,
   getToken,
   downloadFileByBlob,
   printFileByBlob,
   fileStreamToUrl,
   uploadFile,
   listToTree,
   treeArrToList,
   traverseTreeArr,
   sortArr,
   sortManyRuleArr,
   sortTreeArr,
   deepClone,
   getLayoutContentHeight,
   setTableHeight,
   getDounceFunc,
   getThrottleFunc,
   setFixedColumnWidth,
}